#include "tcpsrv.h"
#include "tcpconn.h"
#include "tcphandler.h"
#include "utils.h"
#include "alog.h"
// #include "config.h"

#include <ev.h>
#include <errno.h>
#include <unistd.h>
#include <stddef.h>
#include <assert.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>



struct tcpsrv{
	struct ev_io        ev_accept;
	struct ev_io		ev_accept_4ms;
	struct ev_loop	 	*loop;
	int  fd;			

	int  listen_port;
	int  max_connections;
	pthread_t tid;

	struct tcphandler* handler;
	struct tcpconnect** conns;
};


static float  g_version = 1.1;
static char * g_buildtime = ""__DATE__" "__TIME__;


// connect manager
struct tcpconnect* tcpconn_get_unused(struct tcpsrv *server);

// tcp socket
int  tcp_socket_init(struct tcpsrv *server);
void tcp_accept_cb(EV_P, struct ev_io *w, int revents);
void* tcpsrv_run_fn(void *pdata);


extern struct clog_t global_log;
void init_logs(char *logname, ENUM_LOG_LEVEL level, ENUM_LOG_MODE mode)
{
	log_init(&global_log, logname);

	log_set_level(&global_log, level);
	log_set_mode(&global_log, mode);
}
void uninit_logs()
{
	log_clean(&global_log);
}

struct tcpsrv *tcpsrv_init(int port, struct tcphandler *phandler, 
							int max_connections, double timeout)
{
	int i = 0;
	int ret = 0;
	struct tcpsrv *server = NULL;

	if(!phandler) return NULL;


	LOGTRACE(LOGL_NOTICE, "\n===========================\n tcpsrv_init version:%.3f, build_time=%s\n port=%d, max_conn=%d, timeout=%f, \n===========================\n", 
		g_version, g_buildtime, port, max_connections, timeout);

	if(phandler->env_init)
	{
		ret = phandler->env_init();
		if(ret<0) 
		{
			LOGTRACE(LOGL_FATAL,"tcpsrv env_init failed!!!!\n");
			return NULL;
		}
	}

	server = (struct tcpsrv*)malloc(sizeof(struct tcpsrv));
	if(!server)
	{
		LOGTRACE(LOGL_FATAL,"malloc tcpsrv failed!!!!\n");
		return NULL;
	}
	memset(server, 0, sizeof(struct tcpsrv));
	server->listen_port = port;
	server->max_connections = max_connections;
	server->handler = phandler;
	server->tid = 0;

	server->conns = (struct tcpconnect**)malloc(max_connections*sizeof(struct tcpconnect*));
	for(i=0 ; i<max_connections; i++)
	{
		server->conns[i] = tcpconnect_init(server->handler, timeout);
		if(!server->conns[i])
		{
			LOGTRACE(LOGL_ERROR,"init tcpconnect[%d] failed!\n", i);
			goto eout;
		}
	}

	LOGTRACE(LOGL_NOTICE, "tcpsrv_init ok\n");
	return server;

eout:
	tcpsrv_stop(server);
	return NULL;
}

int tcpsrv_run(struct tcpsrv *server)
{
	int ret = 0;
	if(!server) return -1;

	server->tid = 0;
	ret = pthread_create(&server->tid,NULL,tcpsrv_run_fn,(void*)server);
	if(ret<0)
	{
		LOGTRACE(LOGL_FATAL,"pthread_create loop failed!!!!\n");
		return -11;
	}

	return 0;
}

int tcpsrv_stop(struct tcpsrv *server)
{
	int i = 0 ;
	void *status = NULL;

	if(!server) return -1;

	LOGTRACE(LOGL_NOTICE,"Enter tcpsrv_stop\n");

	ev_io_stop(server->loop, &server->ev_accept);
	ev_break (server->loop, EVBREAK_ALL);

	if(server->tid)
	{
		pthread_join(server->tid, &status);
		LOGTRACE(LOGL_NOTICE,"tcpsrv thread stoped.\n");
		server->tid = 0;
	}

	log_set_level(&global_log, LOGL_FATAL);

	for(i=0; i<server->max_connections; i++)
	{
		if(server->conns[i])
		{
			tcpconnect_clean(server->conns[i]);
			server->conns[i] = NULL;
		}
	}
	free(server->conns);
	server->conns = NULL;

	ev_loop_destroy (server->loop);
	close(server->fd);

	if(server->handler && server->handler->env_clean)
	{
		server->handler->env_clean();
	}
	free(server);
	server = NULL;

	return 0;
}

void* tcpsrv_run_fn(void *pdata) 
{
	int ret = 0;
	struct tcpsrv *server = (struct tcpsrv *)pdata;
	if(!server) return NULL;

	ret = tcpsrv_run_loop(server);
	if(ret<0)
	{
		LOGTRACE(LOGL_ERROR,"tcpsrv_run_loop Failed!!!\n");
	}

	return NULL;
}

int tcpsrv_run_loop(struct tcpsrv *server) 
{
	int ret = 0;
	if(!server) return -1;

	LOGTRACE(LOGL_NOTICE,"Enter tcpsrv_run_loop\n");
	//server->loop = ev_default_loop(0);
	server->loop = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV);

	ret = tcp_socket_init(server);
	if(ret < 0) return ret;

	// start http server.
	ev_io_init(&server->ev_accept,tcp_accept_cb,server->fd,EV_READ);
	ev_io_start(server->loop, &server->ev_accept);
	ev_loop(server->loop, 0);

	LOGTRACE(LOGL_TRACE,"Leave tcpsrv_run_loop\n");
	return 0;
}

//-------------------------//
// tcp server 

int tcp_socket_init(struct tcpsrv *server)
{
	server->fd = socket(AF_INET, SOCK_STREAM, 0);
	if (server->fd < 0) {
		LOGTRACE(LOGL_ERROR,"listen failed(socket)\n");
		return -1;
	}

	// configure server structures and desired listen address
	struct sockaddr_in saddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = INADDR_ANY;
	saddr.sin_port = htons(server->listen_port);

	int reuseaddr_on = 1;
	if (setsockopt(server->fd,SOL_SOCKET,SO_REUSEADDR,&reuseaddr_on,sizeof(saddr)) == -1) {
		LOGTRACE(LOGL_ERROR,"setsockopt failed\n");
		return -2;
	}

	if (bind(server->fd,(struct sockaddr*)&saddr, sizeof(struct sockaddr_in)) < 0) {
		LOGTRACE(LOGL_ERROR,"bind failed\n");
		return -3;
	}
	if (listen(server->fd, 5) < 0) {
		LOGTRACE(LOGL_ERROR,"listen failed(listen)\n");
		return -4;
	}

	return 0;
}

void tcp_accept_cb(EV_P, struct ev_io *w, int revents)
{
	LOGTRACE(LOGL_TRACE,"enter tcp_accept_cb\n");
	
	struct tcpconnect *conn= 0;
	struct tcpsrv  *server = (struct tcpsrv*)w;

	conn = tcpconn_get_unused(server);
    
	if(conn)
    {	
		LOGTRACE(LOGL_NOTICE,"========new conn(%ld)========\n",(long int)conn);

		conn->fd = accept(server->fd, NULL,NULL);
		if(conn->fd > 0)
		{
			if (setnonblock(conn->fd) < 0) {
				LOGTRACE(LOGL_ERROR,"failed to set server socket to nonblock!!!\n");
			}
			
			//
			// TCP_NODELAY:
			// 不使用Nagle算法,不会将小包进行拼接成大包再进行发,直接将小包发送出,会使得小包时候用户体验非常好		
			int opt = 1;
			setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
	
			tcpconnect_open(conn, loop);
		}
		else
		{
			conn = NULL;
			LOGTRACE(LOGL_ERROR,"accept occured,errno=%d\n",errno);
		}
    }
    else{
		LOGTRACE(LOGL_ERROR,"create struct tcpconnect failed\n");
    }
	
	//LOGTRACE(LOGL_TRACE,"leave tcp_accept_cb\n");
}




// http connect manage

struct tcpconnect* tcpconn_get_unused(struct tcpsrv *server)
{
	struct tcpconnect* conn = 0;
	int i = 0 ;
	for( ; i < server->max_connections ; i++)
	{
		if(!server->conns[i]->used){
			conn = server->conns[i];
			break;
		}
	}

	return conn;
}

